﻿using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Security.Cryptography.X509Certificates;


namespace Inlycat
{

    public class ObjectPool : Singleton<ObjectPool>
    {
        private ObjectPool()
        {
            m_PooledObjsDic = new Dictionary<string, IPool>();
        }

        private Dictionary<string, IPool> m_PooledObjsDic;

        public void Init<T>(Func<T> createMethod, Action<T> resetMethod, Action<T> disposeMethod) where T : class
        {
            var poolName = typeof(T).Name;
            if (!m_PooledObjsDic.ContainsKey(poolName))
            {
                m_PooledObjsDic[poolName] = new Pool<T>(createMethod, resetMethod, disposeMethod, 1);
            }
        }

        public T GetOrCreateNew<T>() where T : MonoBehaviour
        {
            var poolName = typeof(T).Name;
            if (!m_PooledObjsDic.ContainsKey(poolName))
            {
                Debug.LogError(string.Format("our pool manager has not the pool name: {0}", poolName));
                return null;
            }
            var pool = m_PooledObjsDic[poolName] as Pool<T>;
            if (pool != null)
            {
                return pool.Pop<T>();
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="content"></param>
        /// <param name="createMethod"></param>
        /// <param name="resetMethod"></param>
        /// <param name="disposeMethod"></param>
        public void Store<T>(T content, Func<T> createMethod = null, Action<T> resetMethod = null, Action<T> disposeMethod = null) where T : Component
        {
            var poolName = typeof(T).Name;
            if (m_PooledObjsDic.ContainsKey(poolName))
            {
                var pool = m_PooledObjsDic[poolName];
                if (pool != null) pool.Push(content);
            }
            else
            {
                var pool = new Pool<T>(createMethod, resetMethod, disposeMethod);
                m_PooledObjsDic[poolName] = pool;
            }
        }


        public void DestroyAll()
        {
            foreach (var value in m_PooledObjsDic.Values)
            {
                value.Clear();
            }
        }

        public void DestroyPool<T>() where T : class
        {
            var poolName = typeof(T).Name;
            if (m_PooledObjsDic.ContainsKey(poolName))
            {
                var pool = m_PooledObjsDic[poolName];
                if (pool != null) pool.Clear();
            }
        }



        private interface IPool
        {
            void Clear();
            T Pop<T>() where T : class;
            void Push<TF>(TF item);
        }

        private class Pool<T> : IPool where T : class
        {
            private static int MaxObjectNum = 20;

            private readonly Func<T> createFunction;
            private readonly Stack<T> pool;
            private readonly Action<T> resetFunction;
            private readonly Action<T> disposeFunction;

            public Pool(Func<T> createFunction, Action<T> resetFunction, Action<T> disposeFunction, int poolCapacity = 10)
            {
                this.createFunction = createFunction;
                this.resetFunction = resetFunction;
                this.disposeFunction = disposeFunction;
                pool = new Stack<T>();
                CreatePoolItems(poolCapacity);
            }



            public int Count
            {
                get
                {
                    return pool.Count;
                }
            }

            private void CreatePoolItems(int numItems)
            {
                for (var i = 0; i < numItems; i++)
                {
                    var item = createFunction();
                    pool.Push(item);
                }
            }

            public void Push<F>(F fitem)
            {
                if (fitem == null || !(fitem is T))
                {
                    Debug.Log("Push-ing null or wrong type item. ERROR");
                    throw new ArgumentNullException();
                }

                T item = fitem as T;

                if (resetFunction != null)
                {
                    resetFunction(item);
                }

                lock (pool)
                {
                    if (Count >= MaxObjectNum)
                    {
                        // when reached the max capacity, we need dispose the item that want to be cached.
                        disposeFunction(item);
                    }
                    else
                    {
                        pool.Push(item);
                    }

                }
            }

            public G Pop<G>() where G : class
            {
                T item;
                lock (pool)
                {
                    if (pool.Count == 0)
                    {
                        return null;
                    }
                    item = pool.Pop();
                }
                return item as G;
            }

            public void Clear()
            {
                int i = this.Count;
                while (i > 0)
                {
                    var item = this.Pop<T>();
                    disposeFunction(item);
                    i--;
                }
            } 
        }

    }

}
